import re
import random
import logging
from waveapi import document 
from waveapi import events
from waveapi import model
from waveapi import robot

logger = logging.getLogger('Tuxaios')
logger.setLevel(logging.DEBUG)

ROLL_RE = re.compile("(?:[0-9]+[dD][0-9]+[e]{0,1}[+-]{0,1}[0-9]*(?![dD])[+]{0,1})*")
ROLL_CHUNK_RE = re.compile('[0-9]+[dD][0-9]+[e]{0,1}')
ROLL_MOD_RE = re.compile('[+-][0-9]+(?![dD])')


def OnRobotAdded(properties, context):
  """Invoked when the robot has been added."""
  root_wavelet = context.GetRootWavelet()
  root_wavelet.CreateBlip().GetDocument().SetText(
  "Usage: enter a die roll string, such as 4d6 and the dicebot will roll the dice for you!  Also supports modifiers, such as 3d4-2, combinations of rolls, such as 4d6+4+3d4, and 'exploding' dice, such as 3d6e.")


def EvaluateOneRoll(s):
  """Evaluates a dice roll string of the form XdY."""
  roll_total = 0 
  rolls = []
  try:
    for roll_chunk in ROLL_CHUNK_RE.findall(s):
      exploding = False
      parts = re.split('[dD]', roll_chunk)
      if len(parts) != 2: return None
      roll_string = parts[0]
      sides_string = parts[1]
      num_rolls = int(roll_string)
      if (num_rolls > 20): return None
      if (sides_string.endswith('e')): 
        sides_string = sides_string[:-1]
        exploding = True
      die_sides = int(sides_string)
      if (die_sides > 1000 or die_sides < 2): return None
      for i in xrange(num_rolls):
        roll = random.randint(1, die_sides)
        roll_total += roll
        rolls.append(str(roll) + '/' + sides_string)
        while (roll == die_sides and exploding):
          roll = random.randint(1, die_sides)
          roll_total += roll
          rolls.append(str(roll) + '/' + sides_string + 'e')
    for roll_mod in ROLL_MOD_RE.findall(s):
      if roll_mod[0] == '-':
        roll_total -= int(roll_mod[1:])
      if roll_mod[0] == '+':
        roll_total += int(roll_mod[1:])
      rolls.append(roll_mod)  
    return str(roll_total), rolls
  except ValueError:
    return None


def OnBlipSubmitted(properties, context):
  """Evaluates a dice roll string of the for XdY[e]+Z+AdB[e]+C."""
  blip = context.GetBlipById(properties['blipId'])
  doc = blip.GetDocument()
  contents = doc.GetText()
  annotations = blip.GetAnnotations()
  padding = 0
  for roll_match in ROLL_RE.finditer(contents):
    if roll_match.start() == roll_match.end(): continue
    roll_result = EvaluateOneRoll(roll_match.group(0))
    # Check to see if this roll_match has already been rolled.
    already_rolled = False
    for ann in annotations:
      if (ann.range.start == roll_match.start() and
          ann.range.end == roll_match.end() - 1 and
          ann.name == 'rolled' and
          ann.value == 'true'):
        already_rolled = True
        continue
    if already_rolled: continue
    if None != roll_result:
      # Shorten the annotation by 1 so that subsequent inserts don't grow it.
      doc.SetAnnotation(document.Range(padding + roll_match.start(),
                                       padding + roll_match.end() - 1),
                        'rolled', 'true')
      result_string = ' = ' + roll_result[0] + ' '
      extra_string = '[' + ','.join(roll_result[1]) + '] '
      doc.InsertText(padding + roll_match.end(), result_string + extra_string)
      bold_start = padding + roll_match.end()
      bold_end = bold_start + len(result_string)
      doc.SetAnnotation(document.Range(bold_start, bold_end),
                        'style/fontWeight', 'bold')
      grey_start = padding + roll_match.end() + len(result_string)
      grey_end = grey_start + len(extra_string) - 1  # Leave a non-grey space
      doc.SetAnnotation(document.Range(grey_start, grey_end), 'style/color',
                        'rgb(200, 200, 200)')
      padding += len(result_string) + len(extra_string)

if __name__ == '__main__':
  myRobot = robot.Robot('tuxaios', 
      image_url='http://tuxaios.appspot.com/assets/avatar.png',
      version='1',
      profile_url='http://tuxaios.appspot.com'
      )
  myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded)
  myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted)
  myRobot.Run()
